package com.ampaiva.db;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.BaseColumns;

public class Dao<T> implements IDao<T> {
    private final Context ctx;

    public Dao(Context ctx) {
        this.ctx = ctx;
    }

    private String getTableName(Class<T> entityClass) {
        String tableName = entityClass.getSimpleName().toLowerCase() + "s";
        return tableName;
    }

    private String getTableName(T entity) {
        String tableName = entity.getClass().getSimpleName().toLowerCase() + "s";
        return tableName;
    }

    private List<T> getCursorEntities(Class<T> entityClass, Cursor cursor, TableInfo tableInfo) {
        try {
            List<T> records = new ArrayList<T>();
            if (cursor.moveToFirst()) {
                do {
                    T entity = entityClass.newInstance();
                    for (int i = 0; i < cursor.getColumnCount(); i++) {
                        String columnName = tableInfo.getColumnsInfo().get(i).getName();
                        String columnType = tableInfo.getColumnsInfo().get(i).getType();
                        setFields(entity, cursor, columnName, columnType, i);
                    }
                    records.add(entity);
                } while (cursor.moveToNext());
            }
            return records;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Object getField(T entity, String field) {
        // TODO: use getDeclaredMethod in order to avoid loop
        Method[] m = entity.getClass().getMethods();
        String fieldName = (field.startsWith("_") ? field.substring(1) : field);
        for (Method method : m) {
            if (!method.getName().equalsIgnoreCase("get" + fieldName))
                continue;
            Class<?> parameterTypes[] = method.getParameterTypes();
            if (parameterTypes.length != 0)
                continue;
            try {
                return method.invoke(entity, new Object[] {});
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        throw new RuntimeException("Field not found: " + fieldName);
    }

    private void setField(T entity, String fieldName, Object value) {
        // TODO: use getDeclaredMethod in order to avoid loop
        Method[] m = entity.getClass().getMethods();
        for (Method method : m) {
            if (!method.getName().equalsIgnoreCase("set" + fieldName))
                continue;
            Class<?> parameterTypes[] = method.getParameterTypes();
            if (parameterTypes.length != 1)
                continue;
            try {
                method.invoke(entity, new Object[] { value });
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void setFields(T entity, Cursor cursor, String columnName, String columnType, int columnOrder) throws Exception {
        Object value = getValue(cursor, columnName, columnType, columnOrder);
        String fieldName = (columnName.startsWith("_") ? columnName.substring(1) : columnName);
        setField(entity, fieldName, value);
    }

    private Object getValue(Cursor cursor, String columnName, String columnType, int columnOrder) {
        // columnOrder = cursor.getColumnIndex(columnName);
        if (columnType.equals("text"))
            return cursor.getString(columnOrder);
        if (columnType.equals("integer"))
            return cursor.getInt(columnOrder);
        if (columnType.equals("real"))
            return cursor.getFloat(columnOrder);
        if (columnType.equals("null"))
            return null;
        throw new RuntimeException("Unknown Field Type " + columnType);
    }

    private ContentValues getContentValues(T entity, TableInfo tableInfo) {
        ContentValues values = new ContentValues();
        for (ColumnInfo columnInfo : tableInfo.getColumnsInfo()) {
            Object value = getField(entity, columnInfo.getName());
            values.put(columnInfo.getName(), value == null ? "" : value.toString());
        }
        return values;
    }

    @Override
    public T get(Uri uri, Class<T> entityClass) {
        SQLiteAdapter db = null;
        try {
            db = SQLiteAdapter.getDBAdapterInstance(ctx);
            db.open();
            Cursor cursor = ctx.getApplicationContext().getContentResolver().query(uri, null, null, null, null);
            TableInfo tableInfo = db.getTableInfo(getTableName(entityClass));
            List<T> records = getCursorEntities(entityClass, cursor, tableInfo);
            cursor.close();
            if (records.size() == 0)
                return null;
            T entity = records.get(0);
            setField(entity, "uri", uri);
            return entity;
        } finally {
            if (db != null)
                db.close();
        }
    }

    @Override
    public void update(T entity) {
        SQLiteAdapter db = null;
        try {
            db = SQLiteAdapter.getDBAdapterInstance(ctx);
            db.open();
            String tableName = getTableName(entity);
            Object _id = getField(entity, BaseColumns._ID);
            String whereClause = "_id=" + _id.toString();
            String[] whereArgs = new String[] {};
            ContentValues values = getContentValues(entity, db.getTableInfo(getTableName(entity)));
            values.remove(BaseColumns._ID);
            db.updateRecordsInDB(tableName, values, whereClause, whereArgs);
        } finally {
            if (db != null)
                db.close();
        }
    }

    @Override
    public T insert(Uri url, Class<T> entityClass) {
        Uri uri = ctx.getApplicationContext().getContentResolver().insert(url, null);
        return get(uri, entityClass);
    }

    @Override
    public void delete(T entity) {
        SQLiteAdapter db = null;
        try {
            db = SQLiteAdapter.getDBAdapterInstance(ctx);
            db.open();
            String tableName = entity.getClass().getSimpleName().toLowerCase() + "s";
            Object _id = getField(entity, BaseColumns._ID);
            String[] whereArgs = new String[] {};
            String whereClause = "_id" + _id.toString();
            db.deleteRecordInDB(tableName, whereClause, whereArgs);
        } finally {
            if (db != null)
                db.close();
        }
    }

    @Override
    public List<T> getAll(Class<T> entityClass) {
        SQLiteAdapter db = null;
        try {
            db = SQLiteAdapter.getDBAdapterInstance(ctx);
            db.open();
            Cursor cursor = db.selectRecordsFromDB(getTableName(entityClass), null, null, null, null, null, null);
            TableInfo tableInfo = db.getTableInfo(getTableName(entityClass));
            List<T> records = getCursorEntities(entityClass, cursor, tableInfo);
            cursor.close();
            return records;
        } finally {
            if (db != null)
                db.close();
        }
    }
}
